package com.example.his.api.front.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.json.JSONUtil;
import com.example.his.api.common.PageUtils;
import com.example.his.api.db.dao.GoodsDao;
import com.example.his.api.db.dao.GoodsSnapshotDao;
import com.example.his.api.db.dao.OrderDao;
import com.example.his.api.db.pojo.GoodsSnapshotEntity;
import com.example.his.api.db.pojo.OrderEntiy;
import com.example.his.api.exception.HisException;
import com.example.his.api.front.service.OrderService;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service("FrontOrderServiceImpl")
public class OrderServiceImpl implements OrderService {
  
  private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
  @Resource
  private OrderDao orderDao;
  
  @Resource
  private GoodsDao goodsDao;
  
  @Resource
  private PaymentServiceImpl paymentService;
  
  @Resource
  private RedisTemplate redisTemplate;
  
  private String paymentNotifyUrl = "/front/order/paymentCallback";
  @Autowired
  private GoodsSnapshotDao goodsSnapshotDao;
  
  private String refundNotifyUrl = "/front/order/refundCallback";
  
  @Override
  @Transactional
  public HashMap createPayment(Map param) {
    Integer customerId = MapUtil.getInt(param, "customerId");
    Integer goodId = MapUtil.getInt(param, "goodsId");
    Integer number = MapUtil.getInt(param, "number");
    
    //    查询当日该用户是否允许下单
    Boolean aBoolean = orderDao.searchIllegalCountInDay(customerId);
    if(aBoolean) {
      return null;
    }
    
    //  查找商品详细信息
    HashMap good = goodsDao.searchSnapshotNeededById(goodId);
    String goodCode = MapUtil.getStr(good, "code");
    String goodTitle = MapUtil.getStr(good, "title");
    String goodDescription = MapUtil.getStr(good, "description");
    String goodImage = MapUtil.getStr(good, "image");
    BigDecimal goodInitialPrice = new BigDecimal(MapUtil.getStr(good, "initialPrice"));
    BigDecimal goodCurrentPrice = new BigDecimal(MapUtil.getStr(good, "currentPrice"));
    String goodRuleName = MapUtil.getStr(good, "ruleName");
    String goodRule = MapUtil.getStr(good, "rule");
    String goodType = MapUtil.getStr(good, "type");
    String goodMd5 = MapUtil.getStr(good, "md5");
    
    String temp = MapUtil.getStr(good, "checkup_1");
    List <Map> goodCheckup_1 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_2");
    List <Map> goodCheckup_2 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_3");
    List <Map> goodCheckup_3 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_4");
    List <Map> goodCheckup_4 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    temp = MapUtil.getStr(good, "checkup");
    List <Map> goodCheckup = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "tag");
    List <String> goodTag = temp != null ? JSONUtil.parseArray(temp).toList(String.class) : null;
    
    //   计算订单金额
    ExpressRunner runner = new ExpressRunner();
    DefaultContext <String, Object> context = new DefaultContext <>();
    context.put("number", number.intValue());
    context.put("price", goodCurrentPrice.toString());
    String amount = null;
    if(goodRule != null) {
      try {
        amount = runner.execute(goodRule, context, null, true, false).toString();
      } catch(Exception e) {
        throw new HisException("规则引擎计算价格失败", e);
      }
    } else {
      amount = goodCurrentPrice.multiply(new BigDecimal(number)).toString();
    }
    
    //   创建微信支付订单
    //    将付款金额转化为分
    int total = NumberUtil.mul(amount, "100").intValue();
    //    生成商品订单流水号
    String outTradeNo = IdUtil.simpleUUID().toUpperCase();
    //    设置付款时间为30分钟
    DateTime dateTime = new DateTime();
    dateTime.offset(DateField.MINUTE, 30);
    String timeExpire = dateTime.toString("YYYY-MM-dd'T'HH:mm:ss.SSS'Z'");
    //    创建支付订单
    ObjectNode objectNode = paymentService.uuifiedOrder(outTradeNo, total, "购买体检套餐", paymentNotifyUrl, timeExpire);
    String codeUrl = objectNode.get("code_url").textValue();
    
    //  创建支付单缓存，设置过期时间
    /**
     * 把支付单的codeurl缓存到redis中，用于将来检测未支付的订单是否可以付款
     * 比如客户进入订单列表页面，想要对未付款的订单付款，我们可以根据是否存在缓存，判定用户是否能付款
     * 可以省去电泳微信支付接口查询付款单状态
     * */
    String key = "codeUrl_" + customerId + "_" + outTradeNo;
    redisTemplate.opsForValue().set(key, codeUrl);
    redisTemplate.expireAt(key, dateTime);
    
    
    //   如果不存在商品快照，创建商品快照
    if(codeUrl != null) {
      /* 根据商品md5查询是否存在商品快照 */
      String _id = goodsSnapshotDao.hasGoodsSnapshot(goodMd5);
      if(_id == null) {
        //         不存在商品快照，创建商品快照
        GoodsSnapshotEntity entity = new GoodsSnapshotEntity();
        entity.setId(goodId);
        entity.setCode(goodCode);
        entity.setTitle(goodTitle);
        entity.setDescription(goodDescription);
        entity.setCheckup_1(goodCheckup_1);
        entity.setCheckup_2(goodCheckup_2);
        entity.setCheckup_3(goodCheckup_3);
        entity.setCheckup_4(goodCheckup_4);
        entity.setCheckup(goodCheckup);
        entity.setImage(goodImage);
        entity.setInitialPrice(goodInitialPrice);
        entity.setCurrentPrice(goodCurrentPrice);
        entity.setType(goodType);
        entity.setTag(goodTag);
        entity.setRuleName(goodRuleName);
        entity.setMd5(goodMd5);
        entity.setRule(goodRule);
        //        保存快照，拿到主键值
        _id = goodsSnapshotDao.insert(entity);
      }
      OrderEntiy orderEntiy = new OrderEntiy();
      orderEntiy.setCustomerId(customerId);
      orderEntiy.setGoodsId(goodId);
      orderEntiy.setSnapshotId(_id); // 关联快照商品
      orderEntiy.setGoodsTitle(goodTitle);
      orderEntiy.setGoodsPrice(goodCurrentPrice);
      orderEntiy.setNumber(number);
      orderEntiy.setAmount(new BigDecimal(amount));
      orderEntiy.setGoodsImage(goodImage);
      orderEntiy.setGoodsDescription(goodDescription);
      orderEntiy.setOutTradeNo(outTradeNo);
      
      //      保存订单记录
      int rows = orderDao.insert(orderEntiy);
      
      if(rows != 0) { // 保存成功
        //  付款二维码图片转换为base64字符串返回给前端
        QrConfig qrConfig = new QrConfig();
        qrConfig.setWidth(300);
        qrConfig.setHeight(300);
        qrConfig.setMargin(2);
        String qrCodeBase64 = QrCodeUtil.generateAsBase64(codeUrl, qrConfig, "jpg");
        
        //        更新商品销量
        HashMap map = new HashMap() {{
          put("id", goodId);
          put("number", number);
        }};
        int updateRows = goodsDao.updateSalesVolume(map);
        if(updateRows != 1) {
          throw new HisException("更新商品销量失败");
        }
        
        return new HashMap() {
          {
            put("qrCodeBase64", qrCodeBase64);
            put("outTradeNo", outTradeNo);
          }
        };
      } else {
        throw new HisException("保存订单失败");
      }
    } else {
      log.error("创建支付订单失败", objectNode);
      throw new HisException("创建支付订单失败");
    }
  }
  
  @Override
  @Transactional
  public boolean updatePayment(Map param) {
    int rows = orderDao.updatePayStatus(param);
    return rows == 1;
  }
  
  @Override
  public Integer searchCustomerId(String outTradeNo) {
    Integer cuntomerId = orderDao.searchCustomerId(outTradeNo);
    return cuntomerId;
  }
  
  @Override
  @Transactional
  public boolean searchPaymentResult(String outTradeNo) {
    String transactionId = paymentService.searchPaymentResult(outTradeNo);
    if(transactionId != null) {
      this.updatePayment(new HashMap() {{
        put("outTradeNo", outTradeNo);
        put("transactionId", transactionId);
      }});
      return true;
    } else {
      return false;
    }
  }
  
  @Override
  public PageUtils searchByPage(Map param) {
    ArrayList <HashMap> list = new ArrayList <>();
    long count = orderDao.searchFrontsOrderCount(param);
    if(count > 0) {
      list = orderDao.searchByPage(param);
    }
    int start = (Integer)param.get("start");
    int length = (Integer)param.get("length");
    PageUtils pageUtils = new PageUtils(list, count, start, length);
    return pageUtils;
  }
  
  
  public boolean refund(Map param) {
    //    先查询订单是否存在退款流水号，避免重读申请退款
    Integer id = MapUtil.getInt(param, "id");
    String outRefundNo = orderDao.searchAlreadyRefund(id);
    //    判断该订单是否申请退款了
    if(outRefundNo != null) {
      return false;
    }
    
    HashMap map = orderDao.searchRefundNeeded(param);
    String transactionId = MapUtil.getStr(map, "transactionId");
    String amount = MapUtil.getStr(map, "amount");
    int total = NumberUtil.mul(amount, "100").intValue();
    //    int refund = total;  // 实际退款款
    int refund = 1; // 退款一分钱
    
    if(transactionId == null) {
      log.error("退款失败，订单不存在交易流水号(transactionId为空)");
      return false;
    }
    
    //    执行退款
    outRefundNo = paymentService.refund(transactionId, refund, total, refundNotifyUrl);
    param.put("outRefundNo", outRefundNo);
    if(outRefundNo != null) {
      //      更新退款单流水号和退款日期
      int rows = orderDao.updateOutRefundNo(param);
      if(rows == 1) {
        return true;
      }
    }
    return false;
  }
  
  
  @Override
  public int insert(Map param) {
    Integer customerId = MapUtil.getInt(param, "customerId");
    
    //    查询当日该用户是否允许下单
    Boolean aBoolean = orderDao.searchIllegalCountInDay(customerId);
    if(aBoolean) {
      return 0;
    }
    
    Integer goodId = MapUtil.getInt(param, "goodsId");
    Integer number = MapUtil.getInt(param, "number");
    
    //  查找商品详细信息
    HashMap good = goodsDao.searchSnapshotNeededById(goodId);
    String goodCode = MapUtil.getStr(good, "code");
    String goodTitle = MapUtil.getStr(good, "title");
    String goodDescription = MapUtil.getStr(good, "description");
    String goodImage = MapUtil.getStr(good, "image");
    BigDecimal goodInitialPrice = new BigDecimal(MapUtil.getStr(good, "initialPrice"));
    BigDecimal goodCurrentPrice = new BigDecimal(MapUtil.getStr(good, "currentPrice"));
    String goodRuleName = MapUtil.getStr(good, "ruleName");
    String goodRule = MapUtil.getStr(good, "rule");
    String goodType = MapUtil.getStr(good, "type");
    String goodMd5 = MapUtil.getStr(good, "md5");
    
    String temp = MapUtil.getStr(good, "checkup_1");
    List <Map> goodCheckup_1 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_2");
    List <Map> goodCheckup_2 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_3");
    List <Map> goodCheckup_3 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "checkup_4");
    List <Map> goodCheckup_4 = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    temp = MapUtil.getStr(good, "checkup");
    List <Map> goodCheckup = temp != null ? JSONUtil.parseArray(temp).toList(Map.class) : null;
    
    temp = MapUtil.getStr(good, "tag");
    //    String goodTag = temp != null ? temp : null;
    List <String> goodTag = temp != null ? JSONUtil.parseArray(temp).toList(String.class) : null;
    //
    //   计算订单金额
    ExpressRunner runner = new ExpressRunner();
    DefaultContext <String, Object> context = new DefaultContext <>();
    context.put("number", number.intValue());
    context.put("price", goodCurrentPrice.toString());
    String amount = null;
    if(goodRule != null) {
      try {
        amount = runner.execute(goodRule, context, null, true, false).toString();
      } catch(Exception e) {
        throw new HisException("规则引擎计算价格失败", e);
      }
    } else {
      amount = goodCurrentPrice.multiply(new BigDecimal(number)).toString();
    }
    
    //   创建微信支付订单
    //    将付款金额转化为分
    //    int total = NumberUtil.mul(amount, "100").intValue();
    //    生成商品订单流水号
    String outTradeNo = IdUtil.simpleUUID().toUpperCase();
    //    设置付款时间为30分钟
    //    DateTime dateTime = new DateTime();
    //    dateTime.offset(DateField.MINUTE, 30);
    //    String timeExpire = dateTime.toString("YYYY-MM-dd'T'HH:mm:ss.SSS'Z'");
    String _id = goodsSnapshotDao.hasGoodsSnapshot(goodMd5);
    if(_id == null) {
      //         不存在商品快照，创建商品快照
      GoodsSnapshotEntity entity = new GoodsSnapshotEntity();
      entity.setId(goodId);
      entity.setCode(goodCode);
      entity.setTitle(goodTitle);
      entity.setDescription(goodDescription);
      entity.setCheckup_1(goodCheckup_1);
      entity.setCheckup_2(goodCheckup_2);
      entity.setCheckup_3(goodCheckup_3);
      entity.setCheckup_4(goodCheckup_4);
      entity.setCheckup(goodCheckup);
      entity.setImage(goodImage);
      entity.setInitialPrice(goodInitialPrice);
      entity.setCurrentPrice(goodCurrentPrice);
      entity.setType(goodType);
      entity.setTag(goodTag);
      entity.setRuleName(goodRuleName);
      entity.setMd5(goodMd5);
      entity.setRule(goodRule);
      //        保存快照，拿到主键值
      _id = goodsSnapshotDao.insert(entity);
    }
    OrderEntiy orderEntiy = new OrderEntiy();
    orderEntiy.setCustomerId(customerId);
    orderEntiy.setGoodsId(goodId);
    orderEntiy.setSnapshotId(_id); // 关联快照商品
    orderEntiy.setGoodsTitle(goodTitle);
    orderEntiy.setGoodsPrice(goodCurrentPrice);
    orderEntiy.setNumber(number);
    orderEntiy.setAmount(new BigDecimal(amount));
    orderEntiy.setAmount(new BigDecimal(100));
    orderEntiy.setGoodsImage(goodImage);
    orderEntiy.setGoodsDescription(goodDescription);
    orderEntiy.setOutTradeNo(outTradeNo);
    
    //      保存订单记录
    int rows = orderDao.insert(orderEntiy);
    return rows;
  }
  
  @Override
  public boolean updateRefundStatus(String outTradeNo) {
    int rows = orderDao.updateRefundStatusByOutRefundNo(outTradeNo);
    return rows == 1;
  }
  
  @Override
  public boolean hasOwnOrder(Map param) {
    Integer id = orderDao.hasOwnOrder(param);
    return id != null;
  }
}


